/*
 * Copyright 2010 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package com.nyway.wehos2r.client.content.music;

import java.util.ArrayList;
import java.util.List;

import com.google.gwt.core.client.GWT;
import com.google.gwt.i18n.client.Constants;
import com.google.gwt.user.client.Random;
import com.google.gwt.view.client.HasData;
import com.google.gwt.view.client.ListDataProvider;
import com.google.gwt.view.client.ProvidesKey;

/**
 * The data source for song information.
 */
public class SongDatabase {

	/**
	 * A song category.
	 */
	public static class Category {

		private final String displayName;

		private Category(String displayName) {
			this.displayName = displayName;
		}

		public String getDisplayName() {
			return displayName;
		}
	}

	/**
	 * Information about a song.
	 */
	public static class SongInfo implements Comparable<SongInfo> {

		/**
		 * The key provider that provides the unique ID of a song.
		 */
		public static final ProvidesKey<SongInfo> KEY_PROVIDER = new ProvidesKey<SongInfo>() {
			@Override
			public Object getKey(SongInfo item) {
				return item == null ? null : item.getId();
			}
		};

		private static int nextId = 0;

		private Category category;
		private final int id;
		private String name;
		private Integer time;
		private String artist;
		private String album;
		private Integer rating;

		public SongInfo(Category category) {
			this.id = nextId;
			nextId++;
			setCategory(category);
		}

		@Override
		public int compareTo(SongInfo o) {
			return (o == null || o.name == null || o.album == null) ? -1
					: -(((o.name) + "||" + (o.album)).compareTo(name + "||"
							+ album));
		}

		@Override
		public boolean equals(Object o) {
			if (o instanceof SongInfo) {
				return id == ((SongInfo) o).id;
			}
			return false;
		}

		/**
		 * @return the category of the conteact
		 */
		public Category getCategory() {
			return category;
		}

		/**
		 * @return the unique ID of the contact
		 */
		public int getId() {
			return this.id;
		}

		@Override
		public int hashCode() {
			return id;
		}

		/**
		 * Set the contact's category.
		 * 
		 * @param category
		 *            the category to set
		 */
		public void setCategory(Category category) {
			assert category != null : "category cannot be null";
			this.category = category;
		}

		public String getName() {
			return name;
		}

		public void setName(String name) {
			this.name = name;
		}

		public Integer getTime() {
			return time;
		}

		public void setTime(Integer time) {
			this.time = time;
		}

		public String getArtist() {
			return artist;
		}

		public void setArtist(String artist) {
			this.artist = artist;
		}

		public String getAlbum() {
			return album;
		}

		public void setAlbum(String album) {
			this.album = album;
		}

		public Integer getRating() {
			return rating;
		}

		public void setRating(Integer rating) {
			this.rating = rating;
		}

	}

	/**
	 * The constants used in this Content Widget.
	 */
	static interface DatabaseConstants extends Constants {
		String[] songDatabaseCategories();
	}

	/**
	 * The singleton instance of the database.
	 */
	private static SongDatabase instance;

	/**
	 * Get the singleton instance of the contact database.
	 * 
	 * @return the singleton instance
	 */
	public static SongDatabase get() {
		if (instance == null) {
			instance = new SongDatabase();
		}
		return instance;
	}

	/**
	 * The provider that holds the list of contacts in the database.
	 */
	private final ListDataProvider<SongInfo> dataProvider = new ListDataProvider<SongInfo>();

	private final Category[] categories;

	/**
	 * Construct a new contact database.
	 */
	private SongDatabase() {
		// Initialize the categories.
		DatabaseConstants constants = GWT.create(DatabaseConstants.class);
		String[] catNames = constants.songDatabaseCategories();
		categories = new Category[catNames.length];
		for (int i = 0; i < catNames.length; i++) {
			categories[i] = new Category(catNames[i]);
		}

		// Generate initial data.
		generateSongs(250);
	}

	/**
	 * Add a new song.
	 * 
	 * @param song
	 *            the song to add.
	 */
	public void addSong(SongInfo song) {
		List<SongInfo> songs = dataProvider.getList();
		// Remove first so we don't add a duplicate.
		songs.remove(song);
		songs.add(song);
	}

	/**
	 * Add a display to the database. The current range of interest of the
	 * display will be populated with data.
	 * 
	 * @param display
	 *            a {@Link HasData}.
	 */
	public void addDataDisplay(HasData<SongInfo> display) {
		dataProvider.addDataDisplay(display);
	}

	/**
	 * Generate the specified number of contacts and add them to the data
	 * provider.
	 * 
	 * @param count
	 *            the number of contacts to generate.
	 */
	public void generateSongs(int count) {
		List<SongInfo> songs = dataProvider.getList();
		for (int i = 0; i < count; i++) {
			songs.add(createSongInfo());
		}
	}

	public ListDataProvider<SongInfo> getDataProvider() {
		return dataProvider;
	}

	/**
	 * Get the categories in the database.
	 * 
	 * @return the categories in the database
	 */
	public Category[] queryCategories() {
		return categories;
	}

	/**
	 * Query all songs for the specified category.
	 * 
	 * @param category
	 *            the category
	 * @return the list of songs in the category
	 */
	public List<SongInfo> queryContactsByCategory(Category category) {
		List<SongInfo> matches = new ArrayList<SongInfo>();
		for (SongInfo song : dataProvider.getList()) {
			if (song.getCategory() == category) {
				matches.add(song);
			}
		}
		return matches;
	}

	/**
	 * Refresh all displays.
	 */
	public void refreshDisplays() {
		dataProvider.refresh();
	}

	private static int SONGINC = 0;

	/**
	 * Create a new random {@link SongInfo}.
	 * 
	 * @return the new {@link SongInfo}.
	 */
	@SuppressWarnings("deprecation")
	private SongInfo createSongInfo() {
		SongInfo song = new SongInfo(nextValue(categories));
		song.setName("song" + (SONGINC++));
		song.setArtist("artist" + (Random.nextInt(42)));
		song.setTime((Random.nextInt(42 + 42) + 42 + 42) * 60);
		song.setAlbum("album" + (Random.nextInt(42)));
		return song;
	}

	/**
	 * Get the next random value from an array.
	 * 
	 * @param array
	 *            the array
	 * @return a random value in the array
	 */
	private <T> T nextValue(T[] array) {
		return array[Random.nextInt(array.length)];
	}

}
